#include "GLee.h"
#include "PortableGL.h"
#include "MeshRenderer.h"

using namespace glutil;

MeshRenderer::MeshRenderer(const geom::PolyMesh * p):m_mesh(p)
{
	m_vbufferID = 0;
	m_ibufferID = 0;
	m_cbufferID = 0;
	m_nbufferID = 0;
	m_meshBSphere = geom::BSphere(m_mesh->getMeshFittedBoundingBox());
}

MeshRenderer::~MeshRenderer()
{
	glDeleteBuffers(1,&m_vbufferID);
	glDeleteBuffers(1,&m_ibufferID);
	glDeleteBuffers(1,&m_cbufferID);
	glDeleteBuffers(1,&m_nbufferID);
}

void MeshRenderer::addInstance(const GLInstanceInfo & m)
{
	m_instances.push_back(m);
}

void MeshRenderer::clearInstances()
{
	m_instances.clear();
}

const Triplet<float>& MeshRenderer::getRot(int instance) const
{
	return m_instances[instance].m_rotation;
}

const Triplet<float>& MeshRenderer::getPos(int instance) const
{
	return m_instances[instance].m_location;
}

void MeshRenderer::setGLRenderState(bool withMaterials)
{
	if(m_vbufferID==0)
		generateVertexBufferObjects();

	glEnableClientState(GL_INDEX_ARRAY);
	glBindBuffer(GL_INDEX_ARRAY, m_ibufferID);
	glIndexPointer(GL_INT, 0,0);
	glEnableClientState(GL_VERTEX_ARRAY);
	// Buffers setup
	glBindBuffer(GL_ARRAY_BUFFER,m_vbufferID);
	glVertexPointer(3,GL_FLOAT,0,0);

	if(withMaterials)
	{
		glEnableClientState(GL_COLOR_ARRAY);
		glBindBuffer(GL_ARRAY_BUFFER, m_cbufferID);
		glColorPointer(3, GL_FLOAT, 0,0);
		glEnableClientState(GL_NORMAL_ARRAY);
		glBindBuffer(GL_ARRAY_BUFFER,m_nbufferID);
		glNormalPointer(GL_FLOAT,0,0);
	}
	else
		glDisable(GL_LIGHTING);
}

void MeshRenderer::unsetGLRenderState()
{
	// Buffers teardown
	glBindBuffer(GL_ARRAY_BUFFER,0);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,0);


	glDisableClientState(GL_NORMAL_ARRAY);
	glDisableClientState(GL_COLOR_ARRAY);
	glDisableClientState(GL_VERTEX_ARRAY);
	glDisableClientState(GL_INDEX_ARRAY);
	glEnable(GL_LIGHTING);
}

void MeshRenderer::render(int instance, bool withMaterials)
{
	setGLRenderState(withMaterials);
	glPushMatrix();
	{
		const util::Triplet<float> &pos = m_instances[instance].m_location;
		glTranslatef(pos[0],pos[1],pos[2]);
		glRotatef(m_instances[instance].m_rotation[1], 0, 1, 0);
		glRotatef(m_instances[instance].m_rotation[0], 1, 0, 0);
		glRotatef(m_instances[instance].m_rotation[2], 0, 0, 1);
		
		float scale = m_instances[instance].m_scale;
		glScalef(scale,scale,scale);

		// Render
		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_ibufferID);
		glDrawRangeElements(GL_TRIANGLES,0, maxPointIndex, 3*m_numberOfTris, GL_UNSIGNED_INT, 0);
	}
	glPopMatrix();
	unsetGLRenderState();
}

void MeshRenderer::renderAll(bool withMaterials, const glutil::FreeCamera * camera, float maxDistance, int selectedTree, float baseScale)
{
	setGLRenderState(withMaterials);
	std::vector<GLInstanceInfo>::const_iterator it;
	std::vector<GLInstanceInfo>::const_iterator end = m_instances.end();
	geom::Frustum frustum(camera->getFrustum());

	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_ibufferID);
	//int numTreesRendered = 0;
	for(it = m_instances.begin(); it!=end; ++it)
	{
		const GLInstanceInfo& m = (*it);
		geom::Point3D p( &(m.m_location[0]));
		geom::BSphere bsphere = m_meshBSphere.translatedAndScaledBy(p,m.m_scale*baseScale);
		if(!frustum.contains(bsphere))
			continue;

		if(p.distanceSquared(camera->position()) > maxDistance * maxDistance && selectedTree != m.m_instanceID)
			continue;

		glPushMatrix();
		glTranslatef(m.m_location[0], m.m_location[1], m.m_location[2]);
		glRotatef(m.m_rotation[1],0,1,0);

		float scale = m.m_scale * baseScale;
		glScalef(scale,scale,scale);

		if(!withMaterials)
		{
			const int &i= m.m_instanceID;
			float red = (((i /128) / 128) << 1 )/ 256.0;
			float green = (((i /128) % 128) << 1 )/ 256.0;
			float blue = (i % 128 << 1) / 256.0;

			glColor3f(red,green,blue);
		}
		if(selectedTree == m.m_instanceID && withMaterials)
		{
			glDisable(GL_LIGHTING);
			glEnable(GL_BLEND);
			glBlendFunc(GL_ONE,GL_ONE);
			glBlendEquation(GL_FUNC_ADD);
		}


		glDrawRangeElements(GL_TRIANGLES,0, maxPointIndex, 3*m_numberOfTris, GL_UNSIGNED_INT, 0);
		if(selectedTree == m.m_instanceID && withMaterials)
		{
			glEnable(GL_LIGHTING);
			glDisable(GL_BLEND);
		}
		glPopMatrix();
	}
	unsetGLRenderState();
}

void MeshRenderer::renderAll(bool withMaterials)
{
	setGLRenderState(withMaterials);
	std::vector<GLInstanceInfo>::const_iterator it;
	std::vector<GLInstanceInfo>::const_iterator end = m_instances.end();
		
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_ibufferID);

	for(it = m_instances.begin(); it!=end; ++it)
	{
		glPushMatrix();
		const GLInstanceInfo& m = (*it);
		glTranslatef(m.m_location[0], m.m_location[1], m.m_location[2]);
		glRotatef(m.m_rotation[1],0,1,0);

		float scale = m.m_scale;
		glScalef(scale,scale,scale);

		glDrawRangeElements(GL_TRIANGLES,0, maxPointIndex, 3*m_numberOfTris, GL_UNSIGNED_INT, 0);

		glPopMatrix();

	}
	unsetGLRenderState();
}

void MeshRenderer::generateVertexBufferObjects()
{
	m_numberOfTris = m_mesh->getNumberOfTris();
	int numPoints = m_mesh->getNumberOfPoints();
	
	float * tva = new float[numPoints * 3]; 	// vertices array
	GLuint   * tia = new GLuint[m_numberOfTris * 3];// indices array
	float * tca = new float[numPoints * 3]; 	// colors array
	float * tna = new float[numPoints * 3]; 	// normals array
	maxPointIndex = 0;
	const util::Triplet<float> &baseColor = m_mesh->baseColor();
	for(int i=0;i<numPoints;++i)
	{
		const geom::MeshPoint &p = m_mesh->getPoint(i);
		tva[i*3+0] = p.p.x;			tva[i*3+1] = p.p.y;			tva[i*3+2] = p.p.z;
		tna[i*3+0] = p.n.dx;		tna[i*3+1] = p.n.dy;		tna[i*3+2] = p.n.dz;

		tca[i*3+0] = baseColor[0]*p.AOFactor;	tca[i*3+1] = baseColor[1]*p.AOFactor;	tca[i*3+2] = baseColor[2]*p.AOFactor;
	}
	
	for(size_t i=0; i<m_numberOfTris; ++i)
	{
		const util::Triplet<int> &t = m_mesh->triangles()[i];
		for(int j=0;j<3;++j)
		if(t[j] > maxPointIndex)
			maxPointIndex = t[j];

		tia[i*3+0] = t[0];			tia[i*3+1] = t[1];			tia[i*3+2] = t[2];
	}

	if(m_vbufferID == 0)
	{
		glGenBuffers(1,&m_vbufferID);
		glGenBuffers(1,&m_ibufferID);
		glGenBuffers(1,&m_cbufferID);
		glGenBuffers(1,&m_nbufferID);
	}

	glBindBuffer(GL_ARRAY_BUFFER,m_vbufferID);
	glBufferData(GL_ARRAY_BUFFER,3*sizeof(float)*numPoints,tva,GL_STATIC_DRAW);

	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,m_ibufferID);
	glBufferData(GL_ELEMENT_ARRAY_BUFFER,3*sizeof(GLuint)*m_numberOfTris,tia,GL_STATIC_DRAW);

	glBindBuffer(GL_ARRAY_BUFFER,m_cbufferID);
	glBufferData(GL_ARRAY_BUFFER,3*sizeof(float)*numPoints,tca,GL_STATIC_DRAW);

	glBindBuffer(GL_ARRAY_BUFFER,m_nbufferID);
	glBufferData(GL_ARRAY_BUFFER,3*sizeof(float)*numPoints,tna,GL_STATIC_DRAW);

	glBindBuffer(GL_ARRAY_BUFFER,0);

	delete [] tva;
	delete [] tia;
	delete [] tca;
	delete [] tna;
}
